{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "286f6ac6",
   "metadata": {},
   "source": [
    "## 手写数字识别"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbd96f87",
   "metadata": {},
   "source": [
    "本章尝试使用更复杂的网络来实现手写数字识别"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "775f80c0",
   "metadata": {},
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "93410dfa",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import random\n",
    "import paddle\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from PIL import Image\n",
    "import gzip\n",
    "import json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "126c48ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\admin\\anaconda3\\lib\\site-packages\\ipykernel\\ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.\n",
      "  and should_run_async(code)\n"
     ]
    }
   ],
   "source": [
    "# 定义数据集读取器\n",
    "def load_data(mode='train'):\n",
    "\n",
    "    # 加载数据\n",
    "    datafile = './data/mnist.json.gz'\n",
    "    print('loading mnist dataset from {} ......'.format(datafile))\n",
    "    data = json.load(gzip.open(datafile))\n",
    "    print('mnist dataset load done')\n",
    "\n",
    "    # 读取到的数据区分训练集，验证集，测试集\n",
    "    train_set, val_set, eval_set = data\n",
    "\n",
    "    # 数据集相关参数，图片高度IMG_ROWS, 图片宽度IMG_COLS\n",
    "    IMG_ROWS = 28\n",
    "    IMG_COLS = 28\n",
    "\n",
    "    if mode == 'train':\n",
    "        # 获得训练数据集\n",
    "        imgs, labels = train_set[0], train_set[1]\n",
    "    elif mode == 'valid':\n",
    "        # 获得验证数据集\n",
    "        imgs, labels = val_set[0], val_set[1]\n",
    "    elif mode == 'eval':\n",
    "        # 获得测试数据集\n",
    "        imgs, labels = eval_set[0], eval_set[1]\n",
    "    else:\n",
    "        raise Exception(\"mode can only be one of ['train', 'valid', 'eval']\")\n",
    "\n",
    "    #校验数据\n",
    "    imgs_length = len(imgs)\n",
    "    assert len(imgs) == len(labels), \\\n",
    "          \"length of train_imgs({}) should be the same as train_labels({})\".format(\n",
    "                  len(imgs), len(labels))\n",
    "\n",
    "    # 定义数据集每个数据的序号， 根据序号读取数据\n",
    "    index_list = list(range(imgs_length))\n",
    "    # 读入数据时用到的batchsize\n",
    "    BATCHSIZE = 100\n",
    "    \n",
    "    # 定义数据生成器\n",
    "    def data_generator():\n",
    "        if mode == 'train':\n",
    "            random.shuffle(index_list)\n",
    "        imgs_list = []\n",
    "        labels_list = []\n",
    "        for i in index_list:\n",
    "            img = np.array(imgs[i]).astype('float32')\n",
    "            label = np.array(labels[i]).astype('float32')\n",
    "            # 在使用卷积神经网络结构时，uncomment 下面两行代码\n",
    "            img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')\n",
    "            label = np.reshape(labels[i], [1]).astype('float32')\n",
    "            \n",
    "            imgs_list.append(img) \n",
    "            labels_list.append(label)\n",
    "            if len(imgs_list) == BATCHSIZE:\n",
    "                yield np.array(imgs_list), np.array(labels_list)\n",
    "                imgs_list = []\n",
    "                labels_list = []\n",
    "\n",
    "        # 如果剩余数据的数目小于BATCHSIZE，\n",
    "        # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch\n",
    "        if len(imgs_list) > 0:\n",
    "            yield np.array(imgs_list), np.array(labels_list)\n",
    "\n",
    "    return data_generator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "789f1ae1",
   "metadata": {},
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09cd5d25",
   "metadata": {},
   "source": [
    "我们使用最经典的全连接神经网络。针对手写数字识别任务，模型组网如下：\n",
    "- 输入层是28X28\n",
    "- 中间层是两个隐含层为10X10，激活函数为Sigmoid函数\n",
    "- 输出层是一个数字，尺寸设置为1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2df8ac1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle.nn.functional as F\n",
    "from paddle.nn import Layer,Linear"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "73d36cf3",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MNIST(Layer):\n",
    "    def __init__(self):\n",
    "        self.super(MNIST,self).__init__()\n",
    "        # 定义两个全连接层\n",
    "        self.fc1 = Linear(in_features=784,out_features=10)\n",
    "        self.fc2 = Linear(in_features=10,out_features=10)\n",
    "        self.fc3 = Linear(in_features=10,out_features=1)\n",
    "    \n",
    "    def forward(self,inputs):\n",
    "        inputs = paddle.reshape(inputs,[inputs.shape[0],784])\n",
    "        outputs1 = self.fc1(inputs)\n",
    "        outputs1 = F.sigmoid(outputs1)\n",
    "        \n",
    "        outputs2 = self.fc2(outputs1)\n",
    "        outputs2 = F.sigmoid(outputs2)\n",
    "        \n",
    "        output_final = self.fc3(output2)\n",
    "        return output_final\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48298ed6",
   "metadata": {},
   "source": [
    "这种网络效果也不是很好，目前，效果最好的应该是卷积神经网络"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "caeee711",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义 SimpleNet 网络结构\n",
    "import paddle\n",
    "from paddle.nn import Conv2D, MaxPool2D, Linear\n",
    "import paddle.nn.functional as F\n",
    "# 多层卷积神经网络实现\n",
    "class MNIST(paddle.nn.Layer):\n",
    "    def __init__(self):\n",
    "        super(MNIST, self).__init__()\n",
    "\n",
    "        # 定义卷积层，输出特征通道out_channels设置为20，卷积核的大小kernel_size为5，卷积步长stride=1，padding=2\n",
    "        self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)\n",
    "        # 定义池化层，池化核的大小kernel_size为2，池化步长为2\n",
    "        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)\n",
    "        # 定义卷积层，输出特征通道out_channels设置为20，卷积核的大小kernel_size为5，卷积步长stride=1，padding=2\n",
    "        self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)\n",
    "        # 定义池化层，池化核的大小kernel_size为2，池化步长为2\n",
    "        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)\n",
    "        # 定义一层全连接层，输出维度是1\n",
    "        self.fc = Linear(in_features=980, out_features=1)\n",
    "\n",
    "    # 定义网络前向计算过程，卷积后紧接着使用池化层，最后使用全连接层计算最终输出\n",
    "    # 卷积层激活函数使用Relu，全连接层不使用激活函数\n",
    "    def forward(self, inputs):\n",
    "        x = self.conv1(inputs)\n",
    "        x = F.relu(x)\n",
    "        x = self.max_pool1(x)\n",
    "        x = self.conv2(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.max_pool2(x)\n",
    "        x = paddle.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3576f85a",
   "metadata": {},
   "source": [
    "### 训练过程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "1b3d2db2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#网络结构部分之后的代码，保持不变\n",
    "def train(model):\n",
    "    model.train()\n",
    "    #调用加载数据的函数，获得MNIST训练数据集\n",
    "    train_loader = load_data('train')\n",
    "    # 使用SGD优化器，learning_rate设置为0.01\n",
    "    opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())\n",
    "    # 训练5轮\n",
    "    EPOCH_NUM = 10\n",
    "    # MNIST图像高和宽\n",
    "    IMG_ROWS, IMG_COLS = 28, 28\n",
    "\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        for batch_id, data in enumerate(train_loader()):\n",
    "            #准备数据\n",
    "            images, labels = data\n",
    "            images = paddle.to_tensor(images)\n",
    "            labels = paddle.to_tensor(labels)\n",
    "            \n",
    "            #前向计算的过程\n",
    "            predicts = model(images)\n",
    "            \n",
    "            #计算损失，取一个批次样本损失的平均值\n",
    "            loss = F.square_error_cost(predicts, labels)\n",
    "            avg_loss = paddle.mean(loss)\n",
    "\n",
    "            #每训练200批次的数据，打印下当前Loss的情况\n",
    "            if batch_id % 200 == 0:\n",
    "                print(\"epoch: {}, batch: {}, loss is: {}\".format(epoch_id, batch_id, avg_loss.numpy()))\n",
    "            \n",
    "            #后向传播，更新参数的过程\n",
    "            avg_loss.backward()\n",
    "            # 最小化loss,更新参数\n",
    "            opt.step()\n",
    "            # 清除梯度\n",
    "            opt.clear_grad()\n",
    "\n",
    "    #保存模型参数\n",
    "    paddle.save(model.state_dict(), 'mnist.pdparams')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d52c4c4c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./data/mnist.json.gz ......\n",
      "mnist dataset load done\n",
      "epoch: 0, batch: 0, loss is: [23.570024]\n",
      "epoch: 0, batch: 200, loss is: [4.085341]\n",
      "epoch: 0, batch: 400, loss is: [2.6122587]\n",
      "epoch: 1, batch: 0, loss is: [2.1669025]\n",
      "epoch: 1, batch: 200, loss is: [3.8048077]\n",
      "epoch: 1, batch: 400, loss is: [3.0402658]\n",
      "epoch: 2, batch: 0, loss is: [4.245047]\n",
      "epoch: 2, batch: 200, loss is: [2.6452148]\n",
      "epoch: 2, batch: 400, loss is: [2.179753]\n",
      "epoch: 3, batch: 0, loss is: [2.3667426]\n",
      "epoch: 3, batch: 200, loss is: [2.4050949]\n",
      "epoch: 3, batch: 400, loss is: [2.3661456]\n",
      "epoch: 4, batch: 0, loss is: [1.706556]\n",
      "epoch: 4, batch: 200, loss is: [2.5298889]\n",
      "epoch: 4, batch: 400, loss is: [1.8829364]\n",
      "epoch: 5, batch: 0, loss is: [2.313572]\n",
      "epoch: 5, batch: 200, loss is: [2.337771]\n",
      "epoch: 5, batch: 400, loss is: [2.2918665]\n",
      "epoch: 6, batch: 0, loss is: [2.337174]\n",
      "epoch: 6, batch: 200, loss is: [2.147835]\n",
      "epoch: 6, batch: 400, loss is: [2.0403585]\n",
      "epoch: 7, batch: 0, loss is: [1.8695378]\n",
      "epoch: 7, batch: 200, loss is: [1.7484696]\n",
      "epoch: 7, batch: 400, loss is: [2.0263906]\n",
      "epoch: 8, batch: 0, loss is: [1.7718979]\n",
      "epoch: 8, batch: 200, loss is: [2.2776895]\n",
      "epoch: 8, batch: 400, loss is: [1.7383025]\n",
      "epoch: 9, batch: 0, loss is: [1.6283102]\n",
      "epoch: 9, batch: 200, loss is: [1.9432307]\n",
      "epoch: 9, batch: 400, loss is: [1.5742939]\n"
     ]
    }
   ],
   "source": [
    "model = MNIST()\n",
    "train(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76d07afe",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72bc7ad3",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
